package com.xcdh.ittools.type;

import com.xcdh.ittools.config.ITToolConfiguration;
import com.xcdh.ittools.reflection.SimpleMetaObject;
import com.xcdh.ittools.utils.MysqlDbUtil;
import com.xcdh.ittools.wrapper.DBColumnWrapper;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.type.TypeHandler;

import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

public class ITToolsResultHandler {

    private ITToolReturnType itToolReturnType ;
    private List<DBColumnWrapper> dbColumnWrappers ;

    private ITToolConfiguration configuration ;


    public ITToolsResultHandler(ITToolReturnType itToolReturnType ,
                                List<DBColumnWrapper> dbColumnWrappers ,
                                ITToolConfiguration configuration
    ){
        this.itToolReturnType = itToolReturnType ;
        this.dbColumnWrappers = dbColumnWrappers ;
        this.configuration = configuration ;
    }

    public Object getResult(ResultSet resultSet) throws SQLException {
        boolean returnsMany = this.isCollection();
        Object result = null ;
        if(returnsMany){
            result = getListResult(resultSet) ;
        }else{
            result = getOne(resultSet);
        }
        return result ;
    }
    
    private Object getListResult(ResultSet resultSet) throws SQLException {
        List<Object> list = new ArrayList<>();
        Class<?> actualTypeArgument = (Class<?>) itToolReturnType.getActualTypeArguments()[0];
        setRowValue(resultSet , list , actualTypeArgument) ;
        return list; 
    }

    private void setRowValue(ResultSet resultSet , List<Object> list , Class clazz) throws SQLException {
        while (resultSet.next()){
            Object object = createElement(clazz) ;
            SimpleMetaObject metaObject = new SimpleMetaObject(object) ;
            for (DBColumnWrapper columnWrapper : dbColumnWrappers) {
                final String property = metaObject.findProperty(columnWrapper.getColumnLabel(), configuration.isMapUnderscoreToCamelCase());
                if(property != null){
                    final Class<?> propertyType = metaObject.getSetterType(property);
                    final TypeHandler<?> typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(propertyType, columnWrapper.getJdbcType());
                    Object result = typeHandler.getResult(resultSet, columnWrapper.getColumnLabel());
                    metaObject.setValue(property , result);
                }
            }
            list.add(object) ;
        }
    }

    private Object getOne(ResultSet resultSet)throws SQLException{
        List<Object> list = new ArrayList<>();
        Class<?> argument = (Class<?>) itToolReturnType.getActualTypeArguments()[0];
        setRowValue(resultSet , list , argument);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }
    
    public Object createElement(Class<?> clazz){
        try {
            return clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public <T> boolean isCollection() {
        return Collection.class.isAssignableFrom((Class<?>) this.itToolReturnType.getRawType());
    }
}
